<template>
  <Teleport to="body">
    <Transition name="fade">
      <div class="my-dialog" :class="dialogClass" v-if="isShow">
        <div class="my-dialog-mask" :style="{ ...maskStyle }"></div>

        <div
          class="my-dialog-container"
          :class="dialogClass + '-container'"
          :style="{ ...containerStyle }"
          ref="outRef"
        >
          <div class="my-dialog-close" v-show="isNeedClose" @click.stop="handleClose">
            <el-icon><Close /></el-icon>
          </div>

          <div class="my-dialog-header">
            <slot name="header"></slot>
          </div>

          <div class="my-dialog-main">
            <slot></slot>
          </div>

          <div class="my-dialog-footer">
            <slot name="footer"></slot>
          </div>
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue'
import { onClickOutside, useScrollLock } from '@vueuse/core' // 依赖vueuse插件里的个别功能

// ====== props/emit
interface IProps {
  dialogClass: string // 必传（用于区分定制化弹窗样式，避免样式污染）
  isNeedClose?: boolean
  canCloseByClickOutside?: boolean
  maskStyle?: object
  containerStyle?: object
}
const props = withDefaults(defineProps<IProps>(), {
  isNeedClose: true,
  canCloseByClickOutside: true
})

const emit = defineEmits(['close'])

// ====== data
const isShow = ref<boolean>(false)
const outRef = ref<any>(null)
const elRef = ref<HTMLElement | null>(document.body)

// ====== vueuse
// 锁定背景滚动
const isLocked = useScrollLock(elRef)
// 支持点击内容区域外部关闭弹窗
onClickOutside(outRef, (_e: Event) => {
  if (props.canCloseByClickOutside) {
    closeDialog()
  }
})

// ====== computed
const isShowCpt = computed(() => isShow.value)

// ====== methods
const openDialog = () => {
  if (isShow.value) return

  isShow.value = true
  isLocked.value = true
}
const closeDialog = (cb?: () => void) => {
  if (!isShow.value) return

  isShow.value = false
  isLocked.value = false
  cb && cb()
}
const handleClose = () => {
  emit('close')
  closeDialog()
}

// ====== expose
defineExpose({
  isShowCpt,
  openDialog,
  closeDialog
})
</script>

<style lang="less" scoped>
.my-dialog {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  z-index: 1997; // 建议比一般脱标元素层级高，比toast等提示层级低

  .my-dialog-mask {
    position: absolute;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
  }

  .my-dialog-container {
    position: absolute;
    top: 20%;
    left: 50%;
    transform: translateX(-50%);
    min-width: 400px;
    min-height: 400px;
    background: #ffffff;
    border-radius: 10px;

    .my-dialog-close {
      position: absolute;
      right: -38px;
      top: 0;
      font-size: 28px;
      color: #ffffff;
      cursor: pointer;
    }
  }
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
